# -*- coding: utf-8 -*-

import re
from datetime import datetime

from django.core.cache import cache
from django.template import RequestContext
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponseRedirect, HttpResponseNotAllowed
from django.shortcuts import get_object_or_404, render_to_response
from django.contrib.auth.decorators import login_required
from django.views.generic.simple import redirect_to
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType

from wiki.forms import ArticleForm, SearchForm
from wiki.models import Article, ChangeSet


# Settings
#   authentication
from django.conf import settings
try:
    WIKI_REQUIRES_LOGIN = settings.WIKI_REQUIRES_LOGIN
except AttributeError:
    WIKI_REQUIRES_LOGIN = True

#  lock duration in minutes
try:
    WIKI_LOCK_DURATION = settings.WIKI_LOCK_DURATION
except AttributeError:
    WIKI_LOCK_DURATION = 15

try:
    from notification import models as notification
except ImportError:
    notification = None

try:
    WIKI_WORD_RE  = settings.WIKI_WORD_RE
except AttributeError:
    from wiki.templatetags.wiki import WIKI_WORD_RE

# compile it only once.
wikiword_re = re.compile(WIKI_WORD_RE)

# default querysets
ALL_ARTICLES = Article.objects.all()
ALL_CHANGES = ChangeSet.non_reverted_objects.all()


def match_wikiword_or_404(s):
    """ Raise Http404 if 's' is not on WikiWord format.
    """
    if wikiword_re.match(s) is None:
        raise Http404()

def get_articles_by_group(article_qs, group_slug=None,
                          group_slug_field=None, group_qs=None):
    group = None
    if group_slug is not None:
        group = get_object_or_404(group_qs,
                                  **{group_slug_field: group_slug})
        article_qs = article_qs.filter(content_type=get_ct(group),
                                       object_id=group.id)
    return article_qs, group

def get_ct(obj):
    """ Return the ContentType of the object's model.
    """
    return ContentType.objects.get(app_label=obj._meta.app_label,
                                   model=obj._meta.module_name)


class ArticleEditLock(object):
    """ A soft lock to edting an article.
    """

    def __init__(self, title, request, message_template=None):
        self.title = title
        self.user_ip = request.META['REMOTE_ADDR']
        self.created_at = datetime.now()

        if message_template == None:
            message_template = ('Possible edit conflict:'
            ' another user started editing this article at %s')

        self.message_template = message_template

        cache.set(title, self, WIKI_LOCK_DURATION*60)

    def create_message(self, request):
        """ Send a message to the user if there is another user
        editing this article.
        """
        if not self.is_mine(request):
            user = request.user
            user.message_set.create(message=self.message_template%self.created_at)

    def is_mine(self, request):
        return self.user_ip == request.META['REMOTE_ADDR']



def article_list(request,
                 group_slug=None, group_slug_field=None, group_qs=None,
                 article_qs=ALL_ARTICLES,
                 SearchFormClass=SearchForm,
                 template='wiki/index.html',
                 extra_context=None,
                 *args, **kw):
    if request.method == 'GET':

        articles, group = get_articles_by_group(
            article_qs, group_slug,
            group_slug_field, group_qs)

        articles = articles.order_by('-created_at')

        search_form = SearchFormClass()

        template_params = {'articles': articles,
                           'search_form': search_form}

        if group_slug is not None:
            template_params['group'] = group
        if extra_context is not None:
            template_params.update(extra_context)

        return render_to_response(template, template_params,
                                  context_instance=RequestContext(request))
    return HttpResponseNotAllowed(['GET'])


def view_article(request, title,
                 ArticleClass=Article, # to create an unsaved instance
                 group_slug=None, group_slug_field=None, group_qs=None,
                 article_qs=ALL_ARTICLES,
                 template='wiki/view.html',
                 extra_context=None,
                 *args, **kw):

    match_wikiword_or_404(title)

    if request.method == 'GET':
        article_args = {'title': title}
        if group_slug is not None:
            group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
            article_args.update({'content_type': get_ct(group),
                                 'object_id': group.id})
        try:
            article = article_qs.get(**article_args)
            if notification is not None:
                is_observing = notification.is_observing(article, request.user)
            else:
                is_observing = False
        except (ArticleClass.DoesNotExist, AttributeError):
            article = ArticleClass(**article_args)
            is_observing = False

        template_params = {'article': article}

        if notification is not None:
            template_params.update({'is_observing': is_observing,
                                    'can_observe': True})

        if group_slug is not None:
            template_params['group'] = group
        if extra_context is not None:
            template_params.update(extra_context)

        return render_to_response(template, template_params,
                                  context_instance=RequestContext(request))
    return HttpResponseNotAllowed(['GET'])


def edit_article(request, title,
                 group_slug=None, group_slug_field=None, group_qs=None,
                 article_qs=ALL_ARTICLES,
                 ArticleClass=Article, # to get the DoesNotExist exception
                 ArticleFormClass=ArticleForm,
                 template='wiki/edit.html',
                 extra_context=None,
                 *args, **kw):
    match_wikiword_or_404(title)
    try:
        article_args = {'title': title}
        if group_slug is not None:
            group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
            article_args.update({'content_type': get_ct(group),
                                 'object_id': group.id})
        article = article_qs.get(**article_args)
    except ArticleClass.DoesNotExist:
        article = None

    if request.method == 'POST':

        form = ArticleFormClass(request.POST, instance=article)
        if form.is_valid():

            if request.user.is_authenticated():
                form.editor = request.user
                if article is None:
                    user_message = u"Your article was created successfully."
                else:
                    user_message = u"Your article was edited successfully."
                request.user.message_set.create(message=user_message)

            if ((article is None) and (group_slug is not None)):
                form.group = group

            new_article, changeset = form.save()

            if group_slug is None:
                url = reverse('wiki_article',
                              args=[new_article.title])
            else:
                url = reverse('wiki_article',
                              kwargs={'title': new_article.title,
                                      'group_slug': group_slug})

            return redirect_to(request, url)

    elif request.method == 'GET':
        user_ip = request.META['REMOTE_ADDR']

        lock = cache.get(title, None)
        if lock is None:
            lock = ArticleEditLock(title, request)
        lock.create_message(request)

        if article is None:
            form = ArticleFormClass(initial={'title': title,
                                             'user_ip': user_ip})
        else:
            form = ArticleFormClass(instance=article,
                                    initial={'user_ip': user_ip})

    template_params = {'form': form}

    if group_slug is not None:
        template_params['group'] = group
    if extra_context is not None:
        template_params.update(extra_context)

    return render_to_response(template, template_params,
                              context_instance=RequestContext(request))


def view_changeset(request, title, revision,
                   group_slug=None, group_slug_field=None, group_qs=None,
                   changes_qs=ALL_CHANGES,
                   template='wiki/changeset.html',
                   extra_context=None,
                   *args, **kw):
    match_wikiword_or_404(title)
    if request.method == "GET":
        article_args = {'article__title': title}
        if group_slug is not None:
            group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
            article_args.update({'article__content_type': get_ct(group),
                                 'article__object_id': group.id})
        changeset = get_object_or_404(
            changes_qs,
            revision=int(revision),
            **article_args)

        template_params = {'article_title': title,
                           'changeset': changeset}

        if group_slug is not None:
            template_params['group'] = group
        if extra_context is not None:
            template_params.update(extra_context)

        return render_to_response(template, template_params,
                                  context_instance=RequestContext(request))
    return HttpResponseNotAllowed(['GET'])


def article_history(request, title,
                    group_slug=None, group_slug_field=None, group_qs=None,
                    article_qs=ALL_ARTICLES,
                    template='wiki/history.html',
                    extra_context=None,
                    *args, **kw):
    match_wikiword_or_404(title)
    if request.method == 'GET':

        article_args = {'title': title}
        if group_slug is not None:
            group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
            article_args.update({'content_type': get_ct(group),
                                 'object_id': group.id})
        article = get_object_or_404(article_qs, **article_args)
        changes = article.changeset_set.filter(
            reverted=False).order_by('-revision')

        template_params = {'article': article,
                           'changes': changes}
        if group_slug is not None:
            template_params['group'] = group
        if extra_context is not None:
            template_params.update(extra_context)

        return render_to_response(template, template_params,
                                  context_instance=RequestContext(request))

    return HttpResponseNotAllowed(['GET'])


def revert_to_revision(request, title,
                       group_slug=None, group_slug_field=None, group_qs=None,
                       article_qs=ALL_ARTICLES,
                       extra_context=None,
                       *args, **kw):
    match_wikiword_or_404(title)
    if request.method == 'POST':

        revision = int(request.POST['revision'])

        article_args = {'title': title}

        if group_slug is not None:
            group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
            article_args.update({'content_type': get_ct(group),
                                 'object_id': group.id})

        article = get_object_or_404(article_qs, **article_args)

        article.revert_to(revision)

        if request.user.is_authenticated():
            request.user.message_set.create(
                message=u"The article was reverted successfully.")

        if group_slug is None:
            url = reverse('wiki_article_history',
                          args=[title])
        else:
            url = reverse('wiki_article_history',
                          kwargs={'title': title,
                                  'group_slug': group_slug})

        return redirect_to(request, url)

    return HttpResponseNotAllowed(['POST'])


def search_article(request,
                   group_slug=None, group_slug_field=None, group_qs=None,
                   article_qs=ALL_ARTICLES,
                   SearchFormClass=SearchForm,
                   extra_context=None,
                   *args, **kw):
    if request.method == 'POST':
        search_form = SearchFormClass(request.POST)
        if search_form.is_valid():
            search_term = search_form.cleaned_data['search_term']
            # go to article by title
            if group_slug is None:
                url = reverse('wiki_article',
                              args=[search_term])
            else:
                url = reverse('wiki_article',
                              kwargs={'title': search_term,
                                      'group_slug': group_slug})

            return redirect_to(request, url)

    return HttpResponseNotAllowed(['POST'])


def recent_changes(request,
                   group_slug=None, group_slug_field=None, group_qs=None,
                   article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES,
                   template='wiki/recentchanges.html',
                   extra_context=None,
                   *args, **kw):
    if request.method == 'GET':
        if  group_slug is not None:
            group = get_object_or_404(group_qs,
                                      **{group_slug_field : group_slug})
            changes_qs = changes_qs.filter(article__content_type=get_ct(group),
                                       article__object_id=group.id)
        template_params = {'changes': changes_qs.order_by('-modified'),
                           }

        return render_to_response(template, template_params,
                                  context_instance=RequestContext(request))
    return HttpResponseNotAllowed(['GET'])


def observe_article(request, title,
                    group_slug=None, group_slug_field=None, group_qs=None,
                    article_qs=ALL_ARTICLES,
                    template='wiki/recentchanges.html',
                    extra_context=None,
                    *args, **kw):
    if request.method == 'POST':

        article_args = {'title': title}
        if group_slug is not None:
            group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
            article_args.update({'content_type': get_ct(group),
                                 'object_id': group.id})
        article = get_object_or_404(article_qs, **article_args)

        notification.observe(article, request.user, 'article_edited',
                             'The article %s, that you observe, has been edited')

        if group_slug is None:
            url = reverse('wiki_article',
                          args=[article.title])
        else:
            url = reverse('wiki_article',
                          kwargs={'title': article.title,
                                  'group_slug': group_slug})

        return redirect_to(request, url)

    return HttpResponseNotAllowed(['POST'])


def stop_observing_article(request, title,
                           group_slug=None, group_slug_field=None, group_qs=None,
                           article_qs=ALL_ARTICLES,
                           template='wiki/recentchanges.html',
                           extra_context=None,
                           *args, **kw):
    if request.method == 'POST':

        article_args = {'title': title}
        if group_slug is not None:
            group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
            article_args.update({'content_type': get_ct(group),
                                 'object_id': group.id})
        article = get_object_or_404(article_qs, **article_args)

        notification.stop_observing(article, request.user)

        if group_slug is None:
            url = reverse('wiki_article',
                          args=[article.title])
        else:
            url = reverse('wiki_article',
                          kwargs={'title': article.title,
                                  'group_slug': group_slug})

        return redirect_to(request, url)
    return HttpResponseNotAllowed(['POST'])


if WIKI_REQUIRES_LOGIN:
    article_list = login_required(article_list)
    view_article = login_required(view_article)
    view_changeset = login_required(view_changeset)
    article_history = login_required(article_history)
    revert_to_revision = login_required(revert_to_revision)
    search_article = login_required(search_article)
    recent_changes = login_required(recent_changes)
    observe_article = login_required(observe_article)
    stop_observing_article = login_required(stop_observing_article)
